﻿using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.TypeSystem;

class MethodGenerator
{
    private readonly CodeWriter _writer;
    private readonly BlockStatement _block;
    private readonly string[] _parameters;
    private readonly IMethod _method;

    public MethodGenerator(CodeWriter writer, IMethod method, CSharpDecompiler decompiler)
    {
        _method = method;
        _writer = writer;
        var tree = decompiler.Decompile(method.MetadataToken);
        var parameterCount = method.Parameters.Count;
        var parameterOffset = 0;
        if (!method.IsStatic)
        {
            parameterCount++;
            parameterOffset++;
        }

        _parameters = new string[parameterCount];
        if (!method.IsStatic)
            _parameters[0] = "self";
        for (var i = 0; i < method.Parameters.Count; i++)
            _parameters[i + parameterOffset] = method.Parameters[i].Name;
        var methodTree = tree.Children.FirstOrDefault() as MethodDeclaration;
        _block = methodTree.Body;
    }

    private void GenerateBlock(BlockStatement block)
    {
        _writer.Indent++;
        foreach (var statement in block.Statements)
        {
            GenerateStatement(statement);
        }

        _writer.Indent--;
    }

    private void GenerateAssignmentExpression(AssignmentExpression assignmentExpression)
    {
        GenerateExpression(assignmentExpression.Left);
        _writer.Code(" = ");
        switch (assignmentExpression.Operator)
        {
            case AssignmentOperatorType.Assign:
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.Add:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" + ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.Subtract:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" - ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.Multiply:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" * ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.Divide:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" / ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.Modulus:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" % ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.ShiftLeft:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" << ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.ShiftRight:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" >> ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.BitwiseAnd:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" & ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.BitwiseOr:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" | ");
                GenerateExpression(assignmentExpression.Right);
                return;
            case AssignmentOperatorType.ExclusiveOr:
                GenerateExpression(assignmentExpression.Left);
                _writer.Code(" ^ ");
                GenerateExpression(assignmentExpression.Right);
                return;
        }
    }

    private void GenerateExpression(Expression expression)
    {
        switch (expression)
        {
            case AnonymousMethodExpression anonymousMethodExpression:
                break;
            case AnonymousTypeCreateExpression anonymousTypeCreateExpression:
                break;
            case ArrayCreateExpression arrayCreateExpression:
                break;
            case ArrayInitializerExpression arrayInitializerExpression:
                break;
            case AsExpression asExpression:
                break;
            case AssignmentExpression assignmentExpression:
                GenerateAssignmentExpression(assignmentExpression);
                break;
            case BaseReferenceExpression baseReferenceExpression:
                break;
            case BinaryOperatorExpression binaryOperatorExpression:
                GenerateBinaryOperatorExpression(binaryOperatorExpression);
                break;
            case CastExpression castExpression:
                break;
            case CheckedExpression checkedExpression:
                break;
            case ConditionalExpression conditionalExpression:
                break;
            case DeclarationExpression declarationExpression:
                break;
            case DefaultValueExpression defaultValueExpression:
                break;
            case DirectionExpression directionExpression:
                break;
            case ErrorExpression errorExpression:
                break;
            case IdentifierExpression identifierExpression:
                _writer.Code(identifierExpression.Identifier);
                break;
            case IndexerExpression indexerExpression:
                break;
            case InterpolatedStringExpression interpolatedStringExpression:
                break;
            case InvocationExpression invocationExpression:
                GenerateInvocationExpression(invocationExpression);
                break;
            case IsExpression isExpression:
                break;
            case LambdaExpression lambdaExpression:
                break;
            case MemberReferenceExpression memberReferenceExpression:
                GenerateMemberReferenceExpression(memberReferenceExpression);
                break;
            case NamedArgumentExpression namedArgumentExpression:
                break;
            case NamedExpression namedExpression:
                break;
            case NullReferenceExpression nullReferenceExpression:
                break;
            case ObjectCreateExpression objectCreateExpression:
                break;
            case OutVarDeclarationExpression outVarDeclarationExpression:
                break;
            case ParenthesizedExpression parenthesizedExpression:
                break;
            case PointerReferenceExpression pointerReferenceExpression:
                break;
            case PrimitiveExpression primitiveExpression:
                _writer.Code(primitiveExpression.Value.ToString());
                break;
            case QueryExpression queryExpression:
                break;
            case SizeOfExpression sizeOfExpression:
                break;
            case StackAllocExpression stackAllocExpression:
                break;
            case SwitchExpression switchExpression:
                break;
            case ThisReferenceExpression thisReferenceExpression:
                break;
            case ThrowExpression throwExpression:
                break;
            case TupleExpression tupleExpression:
                break;
            case TypeOfExpression typeOfExpression:
                break;
            case TypeReferenceExpression typeReferenceExpression:
                break;
            case UnaryOperatorExpression unaryOperatorExpression:
                GenerateUnaryOperatorExpression(unaryOperatorExpression);
                break;
            case UncheckedExpression uncheckedExpression:
                break;
            case UndocumentedExpression undocumentedExpression:
                break;
            case WithInitializerExpression withInitializerExpression:
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(expression));
        }
    }

    private void GenerateMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
    {
        
        
    }

    private void GenerateInvocationExpression(InvocationExpression invocationExpression)
    {
        GenerateExpression(invocationExpression.Target);
        _writer.Code("(");
        var i = 0;
        foreach (var argument in invocationExpression.Arguments)
        {
            if(i>0)
                _writer.Code(", ");
            GenerateExpression(argument);
            i++;
        }
        _writer.Code(")");
    }

    private void GenerateUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression)
    {
        switch (unaryOperatorExpression.Operator)
        {
            case UnaryOperatorType.Not:
                GenerateExpression(unaryOperatorExpression.Expression);
                return;
            case UnaryOperatorType.BitNot:
                _writer.Code(" not ");
                GenerateExpression(unaryOperatorExpression.Expression);
                return;
            case UnaryOperatorType.Minus:
                _writer.Code(" - ");
                GenerateExpression(unaryOperatorExpression.Expression);
                return ;
            case UnaryOperatorType.Plus:
                _writer.Code(" + ");
                GenerateExpression(unaryOperatorExpression.Expression);
                return;
            case UnaryOperatorType.Increment:
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" = ");
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" + 1");
                return;
            case UnaryOperatorType.PostIncrement:
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" = ");
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" + 1");
                return;
            case UnaryOperatorType.Decrement:
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" = ");
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" - 1");
                return;
            case UnaryOperatorType.PostDecrement:
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" = ");
                GenerateExpression(unaryOperatorExpression.Expression);
                _writer.Code(" - 1");
                return;
            case UnaryOperatorType.Dereference:
                _writer.Code(" * ");
                GenerateExpression(unaryOperatorExpression.Expression);
                return;
            case UnaryOperatorType.AddressOf:
                _writer.Code(" & ");
                GenerateExpression(unaryOperatorExpression.Expression);
                return;
            case UnaryOperatorType.Await:
                throw new NotImplementedException();
            case UnaryOperatorType.NullConditional:
                throw new NotImplementedException();
            case UnaryOperatorType.NullConditionalRewrap:
            case UnaryOperatorType.IsTrue:
                throw new NotImplementedException();
            case UnaryOperatorType.SuppressNullableWarning:
                throw new NotImplementedException();
            case UnaryOperatorType.IndexFromEnd:
                throw new NotImplementedException();
            default:
                throw new NotSupportedException("Invalid value for UnaryOperatorType");
        }
    }

    private void GenerateBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression)
    {
        GenerateExpression(binaryOperatorExpression.Left);
        switch (binaryOperatorExpression.Operator)
        {
            case BinaryOperatorType.BitwiseAnd:
                return;
            case BinaryOperatorType.BitwiseOr:
                return ;
            case BinaryOperatorType.ConditionalAnd:
                _writer.Code(" and ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.ConditionalOr:
                _writer.Code(" or ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.ExclusiveOr:
                return ;
            case BinaryOperatorType.GreaterThan:
                _writer.Code(" > ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.GreaterThanOrEqual:
                _writer.Code(" >= ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.Equality:
                _writer.Code(" == ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.InEquality:
                _writer.Code(" ~= ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.LessThan:
                _writer.Code(" < ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.LessThanOrEqual:
                _writer.Code(" <= ");
                GenerateExpression(binaryOperatorExpression.Right);
                return ;
            case BinaryOperatorType.Add:
                return ;
            case BinaryOperatorType.Subtract:
                return ;
            case BinaryOperatorType.Multiply:
                return ;
            case BinaryOperatorType.Divide:
                return ;
            case BinaryOperatorType.Modulus:
                return ;
            case BinaryOperatorType.ShiftLeft:
                return ;
            case BinaryOperatorType.ShiftRight:
                return ;
            case BinaryOperatorType.NullCoalescing:
                return ;
            case BinaryOperatorType.Range:
                return ;
            case BinaryOperatorType.IsPattern:
                return ;
            default:
                throw new NotSupportedException("Invalid value for BinaryOperatorType");
        }
    }

    private void GenerateStatement(Statement statement)
    {
        switch (statement)
        {
            case BlockStatement blockStatement:
                GenerateBlock(blockStatement);
                break;
            case BreakStatement breakStatement:
                _writer.Line("break");
                break;
            case CheckedStatement checkedStatement:
                break;
            case ContinueStatement continueStatement:
                _writer.Line("continue");
                break;
            case DoWhileStatement doWhileStatement:
                _writer.Line("repeat");
                GenerateBlock(doWhileStatement.EmbeddedStatement as BlockStatement);
                _writer.Code("until ");
                GenerateExpression(doWhileStatement.Condition);
                _writer.Line();
                break;
            case EmptyStatement emptyStatement:
                break;
            case ExpressionStatement expressionStatement:
                GenerateExpression(expressionStatement.Expression);
                _writer.Line();
                break;
            case FixedStatement fixedStatement:
                break;
            case ForeachStatement foreachStatement:
                break;
            case ForStatement forStatement:
                foreach (var initializer in forStatement.Initializers)
                    GenerateStatement(initializer);
                _writer.Code("while (");
                GenerateExpression(forStatement.Condition);
                _writer.Line(") do");
                _writer.Indent++;
                GenerateBlock(forStatement.EmbeddedStatement as BlockStatement);
                foreach (var iterator in forStatement.Iterators)
                    GenerateStatement(iterator);
                _writer.Indent--;
                _writer.Line("end");
                break;
            case GotoCaseStatement gotoCaseStatement:
                break;
            case GotoDefaultStatement gotoDefaultStatement:
                break;
            case GotoStatement gotoStatement:
                break;
            case IfElseStatement ifElseStatement:
                break;
            case LabelStatement labelStatement:
                break;
            case LocalFunctionDeclarationStatement localFunctionDeclarationStatement:
                break;
            case LockStatement lockStatement:
                break;
            case ReturnStatement returnStatement:
                break;
            case SwitchStatement switchStatement:
                break;
            case ThrowStatement throwStatement:
                break;
            case TryCatchStatement tryCatchStatement:
                break;
            case UncheckedStatement uncheckedStatement:
                break;
            case UnsafeStatement unsafeStatement:
                break;
            case UsingStatement usingStatement:
                break;
            case VariableDeclarationStatement variableDeclarationStatement:
                foreach (var variable in variableDeclarationStatement.Variables)
                {
                    _writer.Line($"local {variable.Name}");
                }

                break;
            case WhileStatement whileStatement:
                break;
            case YieldBreakStatement yieldBreakStatement:
                break;
            case YieldReturnStatement yieldReturnStatement:
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(statement));
        }
    }

    public void Generate(Context context)
    {
        var methodName = context.GetMethodName(_method);
        var parameters = Join(", ", _parameters);
        if (_method.IsStatic)
            _writer.Line($"local function this.{methodName}({parameters})");
        else
            _writer.Line($"local function this:{methodName}({parameters})");
        GenerateBlock(_block);
        _writer.Line("end");
    }
}